package com.zeta.auth.controller;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.zeta.auth.common.cachekey.CaptchaStringCacheKey;
import com.zeta.auth.common.constants.AuthConstants;
import com.zeta.auth.model.dto.LoginRequestDTO;
import com.zeta.auth.model.entity.UserInfo;
import com.zeta.auth.model.enums.CaptchaStateEnum;
import com.zeta.auth.model.vo.LoginResponseVO;
import com.zeta.auth.service.IUserInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.zetaframework.base.result.ApiResult;
import org.zetaframework.controller.SuperSimpleController;
import org.zetaframework.core.enums.LoginStateEnum;
import org.zetaframework.core.enums.UserStateEnum;
import org.zetaframework.core.model.LoginLogDTO;
import org.zetaframework.core.utils.ContextUtil;
import org.zetaframework.crypto.helper.AESHelper;
import org.zetaframework.log.event.LoginEvent;

import java.util.Objects;

/**
 * 登录认证
 *
 * @author gcc
 */
@Tag(name = "登录认证", description = "1.登录认证")
@RequestMapping("/auth")
@RestController
@RequiredArgsConstructor
public class AuthController extends SuperSimpleController<IUserInfoService, UserInfo> {

    private final ApplicationContext applicationContext;
    private final CaptchaStringCacheKey captchaCacheKey;
    private final AESHelper aesHelper;

    /**
     * 用户登录
     *
     * @param param 登录参数
     * @param request HttpServletRequest
     * @return ApiResult<LoginResult>
     */
    @SecurityRequirements
    @Operation(summary = "登录")
    @PostMapping("/login")
    public ApiResult<LoginResponseVO> login(@RequestBody @Validated LoginRequestDTO param, HttpServletRequest request) {
        // 验证验证码
        String verifyCode = captchaCacheKey.get(param.getKey());
        if (StrUtil.isBlank(verifyCode)) { return fail(CaptchaStateEnum.EXPIRES.getDesc()); }
        if (!param.getCode().equalsIgnoreCase(verifyCode)) {
            return fail(CaptchaStateEnum.ERROR.getDesc());
        }
        captchaCacheKey.delete(param.getKey());

        // 查询用户
        UserInfo user = service.getByAccount(param.getAccount());
        if (user == null) { return fail(LoginStateEnum.FAIL.getDesc()); }
        // 设置用户id，方便记录日志的时候设置创建人
        ContextUtil.setUserId(user.getId());

        // 密码解密
        String password = "";
        try {
            password = aesHelper.decryptStr(param.getPassword());
        } catch (Exception e) {
            logger.warn("登录密码解密失败，pwd:{}", param.getPassword());
        }

        // 比较密码
        if (!service.comparePassword(password, user.getPassword())) {
            LoginEvent event = new LoginEvent(LoginLogDTO.loginFail(param.getAccount(), LoginStateEnum.ERROR_PWD, request));
            applicationContext.publishEvent(event);
            // 密码不正确
            return fail(LoginStateEnum.ERROR_PWD.getDesc());
        }
        // 判断用户状态
        if (Objects.equals(user.getState(), UserStateEnum.FORBIDDEN.getCode())) {
            LoginEvent event = new LoginEvent(LoginLogDTO.loginFail(param.getAccount(), LoginStateEnum.FAIL, AuthConstants.LOGIN_MSG_USER_LOCKED, request));
            applicationContext.publishEvent(event);
            return fail(AuthConstants.LOGIN_MSG_USER_LOCKED);
        }

        // 踢人下线并登录
        StpUtil.kickout(user.getId());
        StpUtil.login(user.getId());

        // 设置token, 方便feign调用时获取token
        ContextUtil.setToken(StpUtil.getTokenValue());
        // 登录日志
        applicationContext.publishEvent(new LoginEvent(LoginLogDTO.loginSuccess(
                param.getAccount(), request
        )));

        // 构造登录返回结果
        return success(new LoginResponseVO(StpUtil.getTokenName(), StpUtil.getTokenValue()));
    }

    /**
     * 注销登录
     *
     * @param request HttpServletRequest
     * @return ApiResult<Boolean>
     */
    @Operation(summary = "注销登录")
    @GetMapping("/logout")
    public ApiResult<Boolean> logout(HttpServletRequest request) {
        UserInfo user = service.getById(StpUtil.getLoginIdAsLong());
        if (user == null) { return fail(AuthConstants.LOGOUT_MSG_USER_ERROR); }

        applicationContext.publishEvent(new LoginEvent(LoginLogDTO.loginFail(
                user.getAccount(), LoginStateEnum.LOGOUT, request
        )));

        // 注销登录
        StpUtil.logout();
        return success(true);
    }

}
